#include <iostream>

#include "tinyxml.h"

#include "TableModule.h"

using namespace std;

Argument::Argument(uint v, const char* n) {
  int tmp;
  val = v;
  name = new char[tmp = (strlen(n)+1)];
  strncpy(name,n,tmp);
}

Argument::~Argument() {
  delete[] name;
}

BitField::BitField(const char* n, int len, int b) {
  int tmp;
  name = new char[tmp = (strlen(n)+1)];
  strncpy(name,n,tmp);
  length = len;
  base = b;
}

BitField::~BitField() {
  delete[] name;

  list<Argument*>::iterator it;
  for (it = args.begin(); it != args.end(); it++)
    delete (*it);
  args.clear();
}

uint BitField::getValFromStr(const char* str) {
  list<Argument*>::iterator it;
  for (it = args.begin(); it != args.end(); it++) {
    if (strcmp((*it)->name,str)==0) {
      return (*it)->val;
    }
  }
  return atoi_prefix(str);
}

void BitField::getValAsStr(uint val, char* out) {
  list<Argument*>::iterator it;
  for (it = args.begin(); it != args.end(); it++) {
    if ((*it)->val == val) {
      strcpy(out, (*it)->name);
      return;
    }
  }
  itoa_prefix(val, out, base);
}

TableField::TableField(const char* n, FieldType ty, int len, int n_e) {
  int tmp;
  if (n) {
    name = new char[tmp = (strlen(n)+1)];
    strncpy(name,n,tmp);
  } else
    name = 0;
  type = ty;
  length = len;
  num_entries = n_e;

  if ((type == FT_VAL_USIGNED) || (type == FT_ADDR) || (type == FT_BITS))
    data = new uint[num_entries];
  else if (type == FT_VAL_SIGNED)
    data = new int[num_entries];
  else if (type == FT_TEXT) {
    data = new char*[num_entries];
    for (tmp=0; tmp<num_entries; tmp++)
      ((char**) data)[tmp] = new char[length+1];
  }
}

TableField::~TableField() {
  int i;
  if (name)
    delete[] name;
  if ((type == FT_VAL_USIGNED) || (type == FT_ADDR))
    delete[] ((uint*) data);
  else if (type == FT_VAL_SIGNED)
    delete[] ((int*) data);
  else if (type == FT_TEXT) {
    for (i=0; i<num_entries; i++)
      delete[] ((char**) data)[i];
    delete[] ((char**) data);
  } else if (type == FT_BITS) {
    delete[] ((uint*) data);
    list<BitField*>::iterator it_bf;
    for (it_bf = bits.begin(); it_bf != bits.end(); it_bf++)
      delete (*it_bf);
    bits.clear();
  }

  list<Argument*>::iterator it;
  for (it = args.begin(); it != args.end(); it++)
    delete (*it);
  args.clear();
}

void TableField::setBase(int b) {
  base = b;
}

void TableField::setAddrType(bool hexAddr) {
  if (hexAddr)
    base = 1;
  else
    base = 2;
}

void TableField::getEntryAsStr(int num, char* out) {
  if (type == FT_VAL_USIGNED) {
    list<Argument*>::iterator it;
    for (it = args.begin(); it != args.end(); it++) {
      if ((*it)->val == ((uint*) data)[num]) {
        strcpy(out, (*it)->name);
        break;
      }
    }
    if (it == args.end()) {
      itoa_prefix(((uint*) data)[num], out, base);
    }
  } else if (type == FT_VAL_SIGNED) {
    itoa_prefix(((int*) data)[num], out, base);
  } else if (type == FT_ADDR) {
    if (base == 1) { // hex addr
      out[0] = '0';
      out[1] = 'x';
      itoa(toRegPointer(((uint*) data)[num]), out+2, 16);
    } else if (base == 2) { // snes addr
      out[0] = '$';
      itoa(((uint*) data)[num], out+1, 16);
    }
  } else if (type == FT_TEXT) {
    strncpy(out, ((char**) data)[num], length+1);
  }
}

void TableField::setEntryAsStr(int num, const char* str, map<string, int> &labels) {
  if (type == FT_VAL_USIGNED) {
    list<Argument*>::iterator it;
    for (it = args.begin(); it != args.end(); it++) {
      if (strcmp((*it)->name,str)==0) {
        ((uint*) data)[num] = (*it)->val;
        break;
      }
    }
    if (it == args.end())
      ((uint*) data)[num] = atoi_prefix(str);
  } else if (type == FT_VAL_SIGNED) {
    ((int*) data)[num] = atoi_prefix(str);
  } else if (type == FT_ADDR) {
    ((uint*) data)[num] = userStrToSnesAddr(str, labels);
  } else if (type == FT_TEXT) {
    if (str)
      strncpy(((char**) data)[num], str, length+1);
    else
      ((char**) data)[num][0] = '\0';
  }
}

void TableField::addArgument(Argument* arg) {
  args.push_back(arg);
}

Table::Table(const char* i, const char* n, const char* en, uint a, int n_e, int b) {
  int tmp;
  id = new char[tmp = (strlen(i)+1)];
  strncpy(id,i,tmp);
  name = new char[tmp = (strlen(n)+1)];
  strncpy(name,n,tmp);
  entryName = new char[tmp = (strlen(en)+1)];
  strncpy(entryName,en,tmp);
  addr = a;
  num_entries = n_e;
  entry_len = 0;
  base = b;
}

Table::~Table() {
  delete[] id;
  delete[] name;
  delete[] entryName;
  list<TableField*>::iterator it;
  for (it = tfs.begin(); it != tfs.end(); it++)
    delete (*it);
  tfs.clear();
}

void Table::addField(TableField* tf) {
  tfs.push_back(tf);
  entry_len += tf->length;
}

int Table::getNumEntries() {
  return num_entries;
}

void Table::readFromRom(Rom& rom) {
  list<TableField*>::iterator it;
  int offset = 0;
  int i;
  for (it = tfs.begin(); it != tfs.end(); it++) {
    for (i=0; i<num_entries; i++) {
      if (((*it)->type == FT_VAL_USIGNED) || ((*it)->type == FT_ADDR) || ((*it)->type == FT_BITS))
        ((uint*) ((*it)->data))[i] = rom.readMulti(addr + offset + (entry_len * i), (*it)->length);
      else if ((*it)->type == FT_TEXT) {
        rom.seek(addr + offset + (entry_len * i));
        readRegString(rom, ((char**) ((*it)->data))[i], (*it)->length);
      }
    }
    offset += (*it)->length;
  }
}

void Table::writeToRom(Rom& rom) {
  list<TableField*>::iterator it;
  int offset = 0;
  int i;
  for (it = tfs.begin(); it != tfs.end(); it++) {
    for (i=0; i<num_entries; i++) {
      if (((*it)->type == FT_VAL_USIGNED) || ((*it)->type == FT_ADDR) || ((*it)->type == FT_BITS)) {
        rom.writeMulti(((uint*) ((*it)->data))[i], addr + offset + (entry_len * i), (*it)->length);
      } else if ((*it)->type == FT_TEXT) {
        rom.seek(addr + offset + (entry_len * i));
        writeRegString(rom, ((char**) ((*it)->data))[i], (*it)->length);
      }
    }
    offset += (*it)->length;
  }
}

void Table::readFromXml(char* dirName, map<string, string> &fns, map<string, int> &labels) {
  char buf[100], buf2[50];
  strncpy(buf2, id, 45);
  strcat(buf2, ".xml");
  getDatFname(fns, id, buf2, dirName, buf);

  TiXmlDocument doc(buf);
  if (!doc.LoadFile()) {
    cerr << "Error: (" << name << ") Failed to load " << buf << endl;
    return;
  }
  TiXmlHandle hDoc(&doc);
  TiXmlElement* elem;
  TiXmlHandle hRoot(0), hEntry(0);

  // Get root element
  elem = hDoc.FirstChildElement().Element();
  hRoot = TiXmlHandle(elem);

  TiXmlElement* entryNode = hRoot.FirstChild(entryName).Element();
  list<TableField*>::iterator it;
  list<BitField*>::iterator it_bf;
  int id, shift;
  uint byte;
  for (; entryNode; entryNode = entryNode->NextSiblingElement()) {
    hEntry = TiXmlHandle(entryNode);
    id = atoi_prefix(entryNode->Attribute("id"));
    for (it = tfs.begin(); it != tfs.end(); it++) {
      if ((*it)->type != FT_BITS) {
        (*it)->setEntryAsStr(id, hEntry.FirstChild((*it)->name).Element()->GetText(), labels);
      } else { 
        byte = 0;
        shift = 8;
        for (it_bf = (*it)->bits.begin(); it_bf != (*it)->bits.end(); it_bf++) {
          shift -= (*it_bf)->length;
          byte |= (*it_bf)->getValFromStr(hEntry.FirstChild((*it_bf)->name).Element()->GetText()) << shift;
        }
        ((uint*) ((*it)->data))[id] = byte;
      }
    }
  }
}

int binary_splice(int val, int start, int len) {
  return ((val >> (8-start-len)) & (0xff >> (8-len)));
}

void Table::writeToXml(char* dirName, map<string, string> &fns) {
  char buf[100], buf2[50];
  int i;

  TiXmlDocument doc;
  TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0", "", "" );
  doc.LinkEndChild( decl );

  TiXmlElement* root = new TiXmlElement("Table");
  root->SetAttribute("id",id);
  doc.LinkEndChild(root);

  TiXmlElement* entry;
  TiXmlElement* field;
  list<TableField*>::iterator it;
  list<BitField*>::iterator it_bf;
  int bpos;
  for (i=0; i<num_entries; i++) {
    entry = new TiXmlElement(entryName);
    itoa_prefix(i, buf, base);
    entry->SetAttribute("id", buf);
    root->LinkEndChild(entry);
    for (it = tfs.begin(); it != tfs.end(); it++) {
      if ((*it)->type != FT_BITS) {
        field = new TiXmlElement((*it)->name);
        (*it)->getEntryAsStr(i, buf);
        field->LinkEndChild( new TiXmlText( buf ) );
        entry->LinkEndChild(field);
      } else {
        bpos=0;
        for (it_bf = (*it)->bits.begin(); it_bf != (*it)->bits.end(); it_bf++) {
          (*it_bf)->getValAsStr(
            binary_splice(
              ((uint*) ((*it)->data))[i], bpos, (*it_bf)->length),
            buf);
          bpos += (*it_bf)->length;
          field = new TiXmlElement((*it_bf)->name);
          field->LinkEndChild( new TiXmlText( buf ) );
          entry->LinkEndChild(field);
        }
      }
    }
  }

  strncpy(buf2, id, 45);
  strcat(buf2, ".xml");
  getDatFname(fns, id, buf2, dirName, buf);
  doc.SaveFile(buf);
}

FieldType strToFieldType(const char* str) {
  if (strcmp(str, "VAL_USIGNED") == 0)
    return FT_VAL_USIGNED;
  else if (strcmp(str, "VAL_SIGNED") == 0)
    return FT_VAL_SIGNED;
  else if (strcmp(str, "ADDR") == 0)
    return FT_ADDR;
  else if (strcmp(str, "TEXT") == 0)
    return FT_TEXT;
  else if (strcmp(str, "BITS") == 0)
    return FT_BITS;
  else
    return FT_VAL_USIGNED;
}

// Loads the table descriptions
TableModule::TableModule(const char* tblFname) {
  TiXmlDocument doc(tblFname);
  if (!doc.LoadFile()) {
    cerr << "Error: (" << getName() << ") Failed to load " << tblFname << endl;
    return;
  }
  TiXmlHandle hDoc(&doc);
  TiXmlElement* elem;
  TiXmlHandle hRoot(0), hTable(0), hFormat(0), hArglist(0);

  // Get root element
  elem = hDoc.FirstChildElement().Element();
  hRoot = TiXmlHandle(elem);

  TiXmlElement* tableNode = hRoot.FirstChild("Table").Element();
  TiXmlElement *fieldNode, *arglistNode, *argNode, *bitNode;
  Table* t;
  TableField *tf;
  BitField *bf;
  FieldType type;
  int base;
  for (; tableNode; tableNode = tableNode->NextSiblingElement()) {
    hTable = TiXmlHandle(tableNode);
    if (hTable.FirstChild("EntryName").Element()->Attribute("index_base") == 0)
      base = 10;
    else
      base = atoi_prefix(hTable.FirstChild("EntryName").Element()->Attribute("index_base"));
    t = new Table(tableNode->Attribute("id"),
                  hTable.FirstChild("Name").Element()->GetText(),
                  hTable.FirstChild("EntryName").Element()->GetText(),
                  atoi_prefix(hTable.FirstChild("Address").Element()->GetText()),
                  atoi_prefix(hTable.FirstChild("Entries").Element()->GetText()),
                  base);
    hFormat = TiXmlHandle(hTable.FirstChild("Format").Element());
    fieldNode = hFormat.FirstChild("Field").Element();
    Argument* arg;
    for (; fieldNode; fieldNode = fieldNode->NextSiblingElement()) {
      type = strToFieldType(fieldNode->Attribute("type"));
      if ((type == FT_VAL_USIGNED) || (type == FT_VAL_SIGNED)) {
        tf = new TableField(fieldNode->GetText(),
                            type,
                            atoi_prefix(fieldNode->Attribute("len")),
                            t->getNumEntries());
        tf->setBase(atoi_prefix(fieldNode->Attribute("base")));
        arglistNode = hTable.FirstChild("ArgList").Element();
        for (; arglistNode; arglistNode = arglistNode->NextSiblingElement()) {
          if (arglistNode->Attribute("field") && (strcmp(arglistNode->Attribute("field"), tf->name) == 0)) {
            hArglist = TiXmlHandle(arglistNode);
            argNode = hArglist.FirstChild("Arg").Element();
            for (; argNode; argNode = argNode->NextSiblingElement()) {
              arg = new Argument(atoi_prefix(argNode->Attribute("val")), argNode->GetText());
              tf->addArgument(arg);
            }
            break;
          }
        }
        t->addField(tf);
      } else if (type == FT_ADDR) {
        tf = new TableField(fieldNode->GetText(),
                            FT_ADDR,
                            atoi_prefix(fieldNode->Attribute("len")),
                            t->getNumEntries());
        tf->setAddrType(strcmp(fieldNode->Attribute("addr_type"), "hex") == 0);
        t->addField(tf);
      } else if (type == FT_TEXT) {
    	tf = new TableField(fieldNode->GetText(),
    			            FT_TEXT,
    			            atoi_prefix(fieldNode->Attribute("len")),
    			            t->getNumEntries());
    	t->addField(tf);
      } else if (type == FT_BITS) {
        tf = new TableField(0, FT_BITS, 1, t->getNumEntries());
        hArglist = TiXmlHandle(fieldNode);
        bitNode = hArglist.FirstChild("Bits").Element();
        for (; bitNode; bitNode = bitNode->NextSiblingElement()) {
          bf = new BitField(bitNode->GetText(),
                                  atoi_prefix(bitNode->Attribute("len")),
                                  atoi_prefix(bitNode->Attribute("base")));
          // Get Arguments for the BitField
          arglistNode = hTable.FirstChild("ArgList").Element();
          for (; arglistNode; arglistNode = arglistNode->NextSiblingElement()) {
            if (arglistNode->Attribute("field") && (strcmp(arglistNode->Attribute("field"), bf->name) == 0)) {
              hArglist = TiXmlHandle(arglistNode);
              argNode = hArglist.FirstChild("Arg").Element();
              for (; argNode; argNode = argNode->NextSiblingElement()) {
                arg = new Argument(atoi_prefix(argNode->Attribute("val")), argNode->GetText());
                bf->args.push_back(arg);
              }
              break;
            }
          }
          tf->bits.push_back(bf);
        }
        t->addField(tf);
      }
    }
    tables.push_back(t);
  }
}

TableModule::~TableModule() {
  list<Table*>::iterator it;
  for (it = tables.begin(); it != tables.end(); it++)
    delete (*it);
  tables.clear();
}

const char* TableModule::getName() {
  return "Data Tables";
}

const char* TableModule::getCredits() {
  return "Written by MrTenda";
}

void TableModule::readFromRom(Rom &rom) {
  list<Table*>::iterator it;
  for (it = tables.begin(); it != tables.end(); it++)
    (*it)->readFromRom(rom);
}

void TableModule::writeToRom(Rom &rom) {
  list<Table*>::iterator it;
  for (it = tables.begin(); it != tables.end(); it++)
    (*it)->writeToRom(rom);
}

void TableModule::readFromDat(char* dirName, map<string, string>&fns, map<string, int> &labels) {
  list<Table*>::iterator it;
  for (it = tables.begin(); it != tables.end(); it++)
    (*it)->readFromXml(dirName, fns, labels);
}

void TableModule::writeToDat(char* dirName, map<string, string> &fns) {
  list<Table*>::iterator it;
  for (it = tables.begin(); it != tables.end(); it++)
    (*it)->writeToXml(dirName, fns);
}
